﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using PP_COM_Wrapper;

namespace Programmer_1
{
    class AppModel
    {
        PSoCProgrammerCOM_Object pp = new PSoCProgrammerCOM_Object();

        public delegate void OnErrorCallback(string strError);

        volatile bool stopReadData;
        volatile bool connected;

        public event _IPSoCProgrammerCOM_ObjectEvents_ConnectedEventHandler Connected;
        public event _IPSoCProgrammerCOM_ObjectEvents_DisconnectedEventHandler Disconnected;

        public enum State { UNKNOWN = 0x01, DISCONNECTED = 0x02, CONNECTED = 0x04, PORT_OPEN = 0x08, POWERED_ON = 0x10, READING_DATA = 0x20 };
        State currentState = State.UNKNOWN;
        public State CurrentState { get { return currentState; } }

        public delegate void StateChangedEventHandler(State oldState, State newState);
        public event StateChangedEventHandler StateChanged;

        public delegate void DataReadEventHandler(int temp, int light, int rate);
        public event DataReadEventHandler DataRead;

        TempAlarm tempAlarm;
        public TempAlarm TempAlarm { get { return tempAlarm; } }

        DataLogger dataLogger;
        public DataLogger DataLogger { get { return dataLogger; } }

        static readonly string[] ZERO_LEN_STR_ARRAY = new string[0];

        public struct Result
        {
            public bool success;
            public string strError;
            string[] ports;
            public string[] Ports { get { return ports == null ? ZERO_LEN_STR_ARRAY : ports; } set { ports = (string[])value; } }
        }

        public AppModel()
        {
            //Init Events here
            pp.Connected += OnProgrammerConnected;
            pp.Disconnected += OnProgrammerDisconnected;
            //Start Client monitor code - optional code, can be passed
            int serverProcessID;
            int clientProcessID = Process.GetCurrentProcess().Id;
            serverProcessID = pp._StartSelfTerminator(clientProcessID);

            tempAlarm = new TempAlarm(this);
            dataLogger = new DataLogger(this);
        }

        public async Task<Result> GetPortsAsync()
        {
            Result result = new Result();
            await Task.Run(() =>
            {
                object ports;
                int hr = pp.GetPorts(out ports, out result.strError);
                result.success = SUCCEEDED(hr);
                result.Ports = (string[])ports;
            });
            // fix for: if device is ON when the app is started the OnConnect is ignored
            if (currentState == State.UNKNOWN && result.success && result.Ports.Length > 0)
            {
                OnProgrammerConnected(null);
            }
            if (!result.success)
            {
                log("GetPorts: {0}", result.strError);
            }
            return result;
        }

        public async Task<Result> OpenPortAsync(string port)
        {
            Result result = new Result();
            await Task.Run(() =>
            {
                int hr = pp.OpenPort(port, out result.strError);
                result.success = SUCCEEDED(hr);
            });
            if (result.success)
            {
                SetState(State.PORT_OPEN);
            }
            else
            {
                log("OpenPort: {0}", result.strError);
            }
            return result;
        }

        public async Task<Result> ClosePortAsync()
        {
            Result result = new Result();
            await Task.Run(() =>
            {
                int hr = pp.ClosePort(out result.strError);
                result.success = SUCCEEDED(hr);
            });
            if (result.success)
            {
                SetState(State.CONNECTED);
            }
            else
            {
                log("ClosePort: {0}", result.strError);
            }
            return result;
        }

        public async Task<Result> PowerOnAsync()
        {
            Result result = new Result();
            await Task.Run(() =>
            {
                int hr = pp.PowerOn(out result.strError);
                result.success = SUCCEEDED(hr);
            });
            if (result.success)
            {
                SetState(State.POWERED_ON);
            }
            else
            {
                log("PowerOn: {0}", result.strError);
            }
            return result;
        }

        public async Task<Result> PowerOffAsync()
        {
            Result result = new Result();
            await Task.Run(() =>
            {
                int hr = pp.PowerOff(out result.strError);
                result.success = SUCCEEDED(hr);
            });
            if (result.success)
            {
                SetState(State.PORT_OPEN);
            }
            else
            {
                log("PowerOff: {0}", result.strError);
            }
            return result;
        }

        public void ReadDataAsync(int samplesPerSecond, OnErrorCallback onErrorCallback)
        {
            Task.Run(() =>
            {
                _ReadData(samplesPerSecond, onErrorCallback);
            });
        }

        private void _ReadData(int samplesPerSecond, OnErrorCallback onErrorCallback)
        {
            stopReadData = false;
            SetState(State.READING_DATA);
            while (!stopReadData)
            {
                int startTime = DateTime.Now.Millisecond;

                object data = null;
                string strError = null;
                int hr = 0;
                try
                {
                    hr = pp.I2C_ReadData(0, 4, out data, out strError);
                }
                catch (System.Runtime.InteropServices.InvalidComObjectException ex)
                {
                    // exception caught when app is closed while reading in progress
                    log("I2C_ReadData: {0}", ex);
                    onErrorCallback("I2C_ReadData: " + ex);
                    SetState(State.POWERED_ON);
                    return;
                }
                if (!SUCCEEDED(hr))
                {
                    log("I2C_ReadData: {0}", strError);
                    onErrorCallback("I2C_ReadData: " + strError);
                    SetState(State.POWERED_ON);
                    return;
                }
                byte[] bytes = (byte[])data;
                int temp = 256 * bytes[0] + bytes[1];
                int light = 256 * bytes[2] + bytes[3];

                // assuming I2C_ReadData takes no time
                // 1 sec = 1000 mills
                if (samplesPerSecond == 0) samplesPerSecond = 1; // to avoid /0
                int sleep = 1000 / samplesPerSecond;
                Thread.Sleep(sleep);

                int endTime = DateTime.Now.Millisecond;
                int elapsedTime = endTime - startTime;
                if (elapsedTime == 0) elapsedTime = 1; // to avoid /0
                // notify all observers (UI + logger + alarm)
                if (DataRead != null)
                    DataRead(temp, light, 1000 / elapsedTime);
            }
            SetState(State.POWERED_ON);
        }

        public void StopReadData()
        {
            stopReadData = true;
            // stop logging data
            if (dataLogger.CurrentState == DataLogger.State.LOGGING_ON)
                dataLogger.StopLogData();
            // turn temp alarm off
            if (tempAlarm.CurrentState == TempAlarm.State.TURNED_ON)
                tempAlarm.TurnOff();
        }

        void OnProgrammerConnected(string progID)
        {
            connected = true;
            SetState(State.CONNECTED);
            if (Connected != null) Connected(progID);
        }

        void OnProgrammerDisconnected(string progID)
        {
            connected = false;
            SetState(State.DISCONNECTED);
            StopReadData();
            dataLogger.StopLogData();
            tempAlarm.TurnOff();
            if (Disconnected != null) Disconnected(progID);
        }

        void SetState(State newState)
        {
            if (!connected)
            {
                newState = State.DISCONNECTED;
            }
            State oldState = this.currentState;
            this.currentState = newState;
            if (oldState != newState)
            {
                StateChanged(oldState, newState);
            }
        }

        static bool SUCCEEDED(int hr)
        {
            return hr >= 0;
        }

        static void log(string format, params object[] args)
        {
            Debug.WriteLine(format, args);
        }
    }
}
